/*
 * Copyright (c) 2001, University of Washington, Department of
 * Computer Science and Engineering.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * 3. Neither name of the University of Washington, Department of
 * Computer Science and Engineering nor the names of its contributors
 * may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package one.radio;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

import one.world.binding.BindingRequest;
import one.world.binding.BindingResponse;
import one.world.binding.Duration;
import one.world.binding.LeaseMaintainer;
import one.world.binding.UnknownResourceException;

import one.world.core.Component;
import one.world.core.ComponentDescriptor;
import one.world.core.Environment;
import one.world.core.Event;
import one.world.core.EventHandler;
import one.world.core.ExceptionalEvent;
import one.world.core.ExportedDescriptor;
import one.world.core.ImportedDescriptor;

import one.world.env.EnvironmentEvent;

import one.world.io.Query;

import one.world.rep.DiscoveredResource;
import one.world.rep.RemoteDescriptor;
import one.world.rep.RemoteEvent;
import one.world.rep.RemoteReference;

import one.world.util.AbstractHandler;
import one.world.util.Operation;
import one.world.util.SystemUtilities;
import one.world.util.Timer;

/**
 * Multicasts an audio stream generated by an {@link AudioSource} to a
 * one.radio channel.  The AudioSource may either capture audio or play
 * audio tuples from a sub-environment specified by &lt;path&gt;.
 *
 * <p>Usage: AudioSender [username] [channel] &lt;path&gt;<p>
 *
 * <p><b>Imported and Exported Event Handlers</b></p>
 *
 * <p>Exported event handlers:<dl>
 *    <dt>main</dt>
 *    <dd>The main environment event handler.
 *        </dd>
 *    <dt>source</dt>
 *    <dd>Accepts {@link Message}s.
 *        </dd>
 * </dl></p>
 *
 * <p>Imported event handlers:<dl>
 *    <dt>request</dt>
 *    <dd>The environment request handler.
 *        </dd>
 *    <dt>sourceControl</dt>
 *    <dd>Accepts {@link AudioSource.ControlEvent}s.
 *        </dd>
 * </dl></p>
 *
 * @version  $Revision: 1.9 $
 * @author   Janet Davis
 */
public final class AudioSender extends Component {

  // =======================================================================
  //                           The main handler
  // =======================================================================

  /** The main exported event handler. */
  final class MainHandler extends AbstractHandler {

    /** Handle the specified event. */
    protected boolean handle1(Event e) {

      if (e instanceof EnvironmentEvent) {
        EnvironmentEvent ee = (EnvironmentEvent)e;

        if (EnvironmentEvent.ACTIVATED == ee.type
	    || EnvironmentEvent.CLONED == ee.type
	    || EnvironmentEvent.MOVED  == ee.type
	    || EnvironmentEvent.RESTORED == ee.type) {

          // Start by exporting myself.
	  new Operation(timer, request, this)
	      .handle(new BindingRequest(null, null, 
	                                 new RemoteDescriptor(source),
                                         Duration.FOREVER));
          return true;

	} else if (EnvironmentEvent.STOP == ee.type) {
	  // Stop the audio source.
	  sourceOperation.handle(
	      new AudioSource.ControlEvent(this, ee, 
	                                   AudioSource.ControlEvent.STOP));
          return true;
	}

      } else if (e instanceof BindingResponse) {
          BindingResponse br = (BindingResponse)e;

          synchronized (lock) {
            // Get the resource.
	    sourceReference = (RemoteReference)br.resource;

	    // Make a lease maintainer.
	    leaseMaintainer = new LeaseMaintainer(br.lease, br.duration,
                                                  this, null, timer);
          }
        
          // Start the audio source.
          sourceOperation.handle(
	      new AudioSource.ControlEvent(this, null,
	                                   AudioSource.ControlEvent.START));
          return true;

      } else if (e instanceof AudioSource.ControlEvent) {
        AudioSource.ControlEvent ce = (AudioSource.ControlEvent)e;

	switch (ce.type) {
	case AudioSource.ControlEvent.STARTED:
	  // All started.
	  SystemUtilities.debug("AudioSource started");
	  return true;

	case AudioSource.ControlEvent.STOPPED:
	  releaseResources();

          // All stopped.
	  respond((Event)e.closure, 
	          new EnvironmentEvent(this, null, 
		  		       EnvironmentEvent.STOPPED,
		                       getEnvironment().getId()));
          return true;
        }

      } else if (e instanceof ExceptionalEvent) {
        // Log and shut down
	SystemUtilities.debug("AudioSender got unexpected exception; "
	                      + "shutting down");
        SystemUtilities.debug(((ExceptionalEvent)e).x);
	releaseResources();
	request.handle(new EnvironmentEvent(this, null,
	                                    EnvironmentEvent.STOPPED,
					    getEnvironment().getId()));
        return true;
      }

      return false;
    }
  }


  // =======================================================================
  //                           The source handler
  // =======================================================================

  /** The source exported event handler. */
  final class SourceHandler extends AbstractHandler {

    /** Handle the specified event. */
    protected boolean handle1(Event e) {

      if (e instanceof Message) {
        Message message = (Message)e;
        message.source = sourceReference;
        request.handle(
	    new RemoteEvent(this, null, resource, message, true));
	return true;
	
      } else if (e instanceof RemoteEvent) {
        RemoteEvent re = (RemoteEvent)e;
        if (re.event instanceof ExceptionalEvent) {
	  handle(re.event);
	  return true;
	}

      } else if ((e instanceof ExceptionalEvent) && 
            (((ExceptionalEvent)e).x instanceof UnknownResourceException)) {
        // Do nothing
	return true;
      }

      return false;
    }
  }


  // =======================================================================
  //                           Descriptors
  // =======================================================================

  /** The component descriptor. */
  private static final ComponentDescriptor SELF =
    new ComponentDescriptor("one.radio.AudioSender",
                            "Audio sender",
                            true);

  /** The exported event handler descriptor for the main handler. */
  private static final ExportedDescriptor MAIN =
    new ExportedDescriptor("main",
                           "Main handler",
                           new Class[] { EnvironmentEvent.class },   
                           null,  
                           false);

  /** The exported event handler descriptor for the source handler. */
  private static final ExportedDescriptor SOURCE =
    new ExportedDescriptor("source",
                           "Audio source handler",
                           new Class[] { AudioMessage.class },  
                           null, 
                           false);


  /** The imported event handler descriptor for the request handler. */
  private static final ImportedDescriptor REQUEST =
    new ImportedDescriptor("request",
                           "Environment request handler",
                           new Class[] { EnvironmentEvent.class },
                           null,   
                           false,
                           false);
  /** 
   * The imported event handler descriptor for the sourceControl 
   * handler. 
   */
  private static final ImportedDescriptor SOURCECONTROL =
    new ImportedDescriptor("sourceControl",
                           "Audio source control handler",
                           new Class[] { AudioSource.ControlEvent.class },
                           null,
                           false,
                           false);


  // =======================================================================
  //                           Instance fields
  // =======================================================================

  /**
   * The main exported event handler.
   *
   * @serial  Must not be <code>null</code>.
   */
  final EventHandler       main;

  /**
   * The source exported event handler.
   *
   * @serial  Must not be <code>null</code>.
   */
  final EventHandler       source;

  /**
   * The request imported event handler.
   *
   * @serial  Must not be <code>null</code>.
   */
  final Component.Importer request;

  /**
   * The sourceControl imported event handler.
   *
   * @serial  Must not be <code>null</code>.
   */
  final Component.Importer sourceControl;

  /** A timer component. */
  final Timer timer;

  /** An operation for the audio source. */
  final Operation sourceOperation;

  /** The channel resource. */
  final DiscoveredResource resource;

  /** A remote reference for the audio source handler. */
  transient RemoteReference sourceReference;

  /** A lease maintainer for the audio handler remote reference. */
  transient LeaseMaintainer leaseMaintainer;

  /** A lock object. */
  transient Object lock;


  // =======================================================================
  //                           Constructor
  // =======================================================================

  /**
   * Create a new instance of <code>AudioSender</code>.
   *
   * @param  env     The environment for the new instance.
   * @param  channel The channel name.
   */
  public AudioSender(Environment env, String channel) {
    super(env);
    main = declareExported(MAIN, new MainHandler());
    source = declareExported(SOURCE, new SourceHandler());
    sourceControl = declareImported(SOURCECONTROL);
    request = declareImported(REQUEST);

    this.resource = new DiscoveredResource( 
        new Query(
	    new Query("", Query.COMPARE_HAS_SUBTYPE, Channel.class),
	    Query.BINARY_AND,
	    new Query("name", Query.COMPARE_EQUAL, channel)),
	true);

    // Get a timer.
    timer = getTimer();

    lock = new Object();

    // Set up useful operations.
    sourceOperation = new Operation(timer, sourceControl, main);
  }


  /** Releases resources. */
  void releaseResources() {
    synchronized (lock) {
      if (leaseMaintainer != null) {
	leaseMaintainer.cancel();
	leaseMaintainer = null;
      }
    }
  }

  /**
   * Serialize this audio source.
   *
   * @serialData    The default fields while holding the lock.
   */
  protected void writeObject(ObjectOutputStream out) throws IOException {
    synchronized (lock) {
      out.defaultWriteObject();
    }
  }

  /** Deserialize this audio source. */
  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {

    // Read the non-transient fields.
    in.defaultReadObject();
    
    // Restore transients.
    lock = new Object();
  }

  // =======================================================================
  //                           Component support
  // =======================================================================

  /** Get the component descriptor. */
  public ComponentDescriptor getDescriptor() {
    return (ComponentDescriptor)SELF.clone();
  }

  /** 
   * Initialize an environment with this component.
   *
   * @param env      The environment to initialize.
   * @param closure  A string array containing the username,
   *                 the name of the channel to send to, and, optionally,
   *                 the path of a sub-environment from which to play
   *                 audio tuples.  If no path is specified, audio will be
   *                 captured from the microphone instead.
   */
  public static void init(Environment env, Object closure) 
          throws Throwable {
 
    String[] args = (String[])closure;
    if (args.length < 2 || args.length > 3) {
      throw new IllegalArgumentException(
          "Usage: AudioSender [username] [channel] <path>");
    }

    AudioSender sender = new AudioSender(env, args[1]);

    AudioSource source;
    if (args.length > 2) {
      source = new AudioSource(env, args[0], args[1], args[2]);
    } else {
      source = new AudioSource(env, args[0], args[1]);
    }

    env.link("main", "main", sender);
    sender.link("request", "request", env);

    sender.link("sourceControl", "control", source);
    source.link("audio", "source", sender);
    source.link("request", "request", env);
  }
}
